package Cryptographic;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import Verifier.GeneralUtil;

/**
 * 
 * @author Or Stern
 *
 */
public class PRG {
	HashFunction hashFunction;
	
	/**
	 * 
	 * @param hash the HushFunction which we will use in the PRG
	 */
	public PRG(HashFunction hash)
	{
		hashFunction = hash;
	}
	
	/**
	 * 
	 * @param s byte array contains the seed.
	 * @param from determines where the sub-array should begin.
	 * @param to determines where the sub-array should end.
	 * @return Generates a random number longer than the seed. 
	 * @throws Exception If the seed length is not equal to this.seedlen.
	 */
	public byte[] compute(byte[] s, long from, long to) throws Exception
	{
		int hashOutlen = hashFunction.getOutlen();
		int seedLen = s.length;
		long n_h;
		if (((seedLen % 8) != 0) || ((hashOutlen/8) != seedLen)) {
			
			throw new Exception("Seed length dosent match the hash function.");
		}
		else
			n_h = hashOutlen/8;
		
		/* compute the relevant ri range */
		int first_ri = (int) (from / n_h);
		int last_ri = (int) (to / n_h);
		
		/*create an array of concatenation of the relevant ri*/
		ByteArrayOutputStream riStreamArray = new ByteArrayOutputStream();
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		try
		{
			for (int i = first_ri; i <= last_ri; i++)
			{
				riStreamArray.write(compute_r(s, i));
			}
			
			
			byte[] riArray = riStreamArray.toByteArray();
			
			/*extract the relevant part */
			long startIndex = first_ri*n_h;
			int newFrom = (int) (from - startIndex);
			int newTo = (int) (to - startIndex);
			
			outputStream.write(riArray, newFrom, newTo - newFrom + 1);
		} catch (Exception e) {
			throw new Exception("A problem occured while computing the PRG output\n" + e.getMessage());
		}
		byte[] output =  outputStream.toByteArray();
		
		return output;
		
	}
	
	private byte[] compute_r(byte[] s, int i) throws Exception {
		ByteArrayOutputStream hashInput = new ByteArrayOutputStream();
		try
		{
			hashInput.write(s);
			hashInput.write(GeneralUtil.intToByteArray(i));
		} catch (Exception e) {
			throw new Exception("cannot compute r_" + i +"\n" + e.getMessage());
		}
		
		return hashFunction.compute(hashInput.toByteArray());
	}

	/**
	 * 
	 * @return The required number of bits in the seed.
	 */
	public int seedlen()
	{
		return hashFunction.getOutlen();
	}	
}
